package com.example.alspringaop.spring.framework.aop.support;

import com.example.alspringaop.spring.framework.aop.aspect.ALAdvice;
import com.example.alspringaop.spring.framework.aop.config.ALAopConfig;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 解析AOP的配置工具类
 */
public class ALAdvisedSupport {

    private ALAopConfig config;

    private Object target;

    private Class targetClass;

    private Pattern pointCutClassPattern;

    private Map<Method,Map<String, ALAdvice>> methodCache;


    public ALAdvisedSupport(ALAopConfig config) {
        this.config = config;
    }

    /**
     * 解析配置文件的方法
     */
    private void parse(){
        //把Spring的Excpress变成Java能够识别的正则表达式
        String pointCut = config.getPointcut()
                .replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");

        //保存专门匹配的Class的正则
        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));

        //享元的共享池
        methodCache = new HashMap<Method,Map<String,ALAdvice>>();

        //保存专门匹配方法的正则
        Pattern pointCutPattern = Pattern.compile(pointCut);

        try{
            Class aspectClass = Class.forName(this.config.getAspectClass());
            Map<String,Method> aspectMethods = new HashMap<String,Method>();

            for(Method method : aspectClass.getMethods()){
                aspectMethods.put(method.getName(),method);
            }

            for(Method method: this.targetClass.getMethods()){
                String methodString = method.toString();
                if(methodString.contains("throws")){
                    methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                }

                Matcher matcher = pointCutPattern.matcher(methodString);
                if(matcher.matches()){
                    Map<String,ALAdvice> advices = new HashMap<>();
                    if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))){
                        advices.put("before",new ALAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectBefore())));
                    }
                    if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))){
                        advices.put("after",new ALAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfter())));
                    }
                    if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))){
                        ALAdvice advice = new ALAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfterThrow()));
                        advice.setThrowName(config.getAspectAfterThrowingName());
                        advices.put("afterThrow",advice);
                    }
                    //跟目标代理类的业务方法和advices建立一个一对多的关联关系，以便在Proxy类中获得
                    methodCache.put(method,advices);
                }
            }


        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     *  根据一个目标代理类的方法，获得其对应的通知
     */
    public Map<String,ALAdvice> getAdvices(Method method, Object o) throws Exception {
        //享元设计模式的应用
        Map<String,ALAdvice> cache = methodCache.get(method);
        if(null == cache){
            Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
            cache = methodCache.get(m);
            this.methodCache.put(m,cache);
        }
        return cache;
    }

    /**
     * 给ApplicationContext首先IOC中的对象初始化时调用，决定要不要生成代理类的逻辑
     * @return
     */
    public boolean pointCutMath(){
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Class getTargetClass() {
        return targetClass;
    }

    public Object getTarget() {
        return target;
    }
}
